#include "pch.h"
#include "GIFDecoder.h"
#include "D2DManager.h"
#include <ppltasks.h>
#include <istream>
using namespace Windows::Storage;
using namespace concurrency;
using namespace Windows::Storage::Streams;

GIFDecoder::GIFDecoder(const wchar_t* fileName) : m_background_color(0, 0, 0)
{
	ThrowIfFailed(
		D2DManager::GetInstance()->GetWICImagingFactory()->CreateDecoderFromFilename(
		fileName,
		NULL,
		GENERIC_READ,
		WICDecodeMetadataCacheOnLoad,
		m_decoder.GetAddressOf()
		));

	ReadMetadata();
}

GIFDecoder::GIFDecoder(IStream* stream) : m_background_color(0, 0, 0)
{
	ThrowIfFailed(
		D2DManager::GetInstance()->GetWICImagingFactory()->CreateDecoderFromStream(
		stream, 
		NULL, 
		WICDecodeMetadataCacheOnLoad, 
		m_decoder.GetAddressOf())
		);

	ReadMetadata();
}


HRESULT GIFDecoder::ReadMetadata()
{
	PROPVARIANT propValue;
	PropVariantInit(&propValue);
	Microsoft::WRL::ComPtr<IWICMetadataQueryReader> pMetadataQueryReader = NULL;

	// Get the frame count
	ThrowIfFailed(m_decoder->GetFrameCount(&m_frames_count));
	
	ThrowIfFailed(m_decoder->GetMetadataQueryReader(&pMetadataQueryReader));

	// Get background color
	if (FAILED(GetBackgroundColor(pMetadataQueryReader.Get())))
	{
		// Default to transparent if failed to get the color
		m_background_color = D2D1::ColorF(0, 0.f);
	}

	// Get global frame size
	ThrowIfFailed(ReadUIntegerMetadata(pMetadataQueryReader.Get(), L"/logscrdesc/Width", m_width));
	
	// Get height
	ThrowIfFailed(ReadUIntegerMetadata(pMetadataQueryReader.Get(), L"/logscrdesc/Height", m_height));


	// Get pixel aspect ratio
	ThrowIfFailed(pMetadataQueryReader->GetMetadataByName(L"/logscrdesc/PixelAspectRatio", &propValue));
	ThrowIfFailed((propValue.vt == VT_UI1 ? S_OK : E_FAIL));
	UINT uPixelAspRatio = propValue.bVal;

	if (uPixelAspRatio != 0)
	{
		// Need to calculate the ratio. The value in uPixelAspRatio 
		// allows specifying widest pixel 4:1 to the tallest pixel of 
		// 1:4 in increments of 1/64th
		FLOAT pixelAspRatio = (uPixelAspRatio + 15.f) / 64.f;

		// Calculate the image width and height in pixel based on the
		// pixel aspect ratio. Only shrink the image.
		if (pixelAspRatio > 1.f)
		{
			m_pixel_width = m_width;
			m_pixel_height = static_cast<UINT>(m_height / pixelAspRatio);
		}
		else
		{
			m_pixel_width = static_cast<UINT>(m_width * pixelAspRatio);
			m_pixel_height = m_height;
		}
	}
	else
	{
		// The value is 0, so its ratio is 1
		m_pixel_width = m_width;
		m_pixel_height = m_height;
	}
			
	PropVariantClear(&propValue);
		
	

	// Get looping information
		// First check to see if the application block in the Application Extension
		// contains "NETSCAPE2.0" and "ANIMEXTS1.0", which indicates the gif animation
		// has looping information associated with it.
		// 
		// If we fail to get the looping information, loop the animation infinitely.
	ThrowIfFailed(pMetadataQueryReader->GetMetadataByName(
															L"/appext/application",
															&propValue));
		if (propValue.vt == (VT_UI1 | VT_VECTOR) &&
			propValue.caub.cElems == 11 &&  // Length of the application block
			(!memcmp(propValue.caub.pElems, "NETSCAPE2.0", propValue.caub.cElems) ||
			!memcmp(propValue.caub.pElems, "ANIMEXTS1.0", propValue.caub.cElems)))
		{
			PropVariantClear(&propValue);

			ThrowIfFailed(pMetadataQueryReader->GetMetadataByName(L"/appext/data", &propValue));
				//  The data is in the following format:
				//  byte 0: extsize (must be > 1)
				//  byte 1: loopType (1 == animated gif)
				//  byte 2: loop count (least significant byte)
				//  byte 3: loop count (most significant byte)
				//  byte 4: set to zero
				if (propValue.vt == (VT_UI1 | VT_VECTOR) &&
					propValue.caub.cElems >= 4 &&
					propValue.caub.pElems[0] > 0 &&
					propValue.caub.pElems[1] == 1)
				{
					m_max_loop_count = MAKEWORD(propValue.caub.pElems[2], propValue.caub.pElems[3]);

					// If the total loop count is not zero, we then have a loop count
					// If it is 0, then we repeat infinitely
					if (m_max_loop_count != 0)
					{
						m_has_loop = TRUE;
					}
				}
			
		}


	PropVariantClear(&propValue);
	return S_OK;
}

HRESULT GIFDecoder::GetBackgroundColor(IWICMetadataQueryReader *pMetadataQueryReader)
{
	DWORD dwBGColor;
	BYTE backgroundIndex = 0;
	WICColor rgColors[256];
	UINT cColorsCopied = 0;
	PROPVARIANT propVariant;
	PropVariantInit(&propVariant);
	ComPtr<IWICPalette> pWicPalette = NULL;

	// If we have a global palette, get the palette and background color
	ThrowIfFailed(pMetadataQueryReader->GetMetadataByName(L"/logscrdesc/GlobalColorTableFlag", &propVariant));

	ThrowIfFailed((propVariant.vt != VT_BOOL || !propVariant.boolVal) ? E_FAIL : S_OK);
	PropVariantClear(&propVariant);

	// Background color index
	ThrowIfFailed(pMetadataQueryReader->GetMetadataByName(L"/logscrdesc/BackgroundColorIndex", &propVariant));
	
	ThrowIfFailed((propVariant.vt != VT_UI1) ? E_FAIL : S_OK);
	backgroundIndex = propVariant.bVal;
	PropVariantClear(&propVariant);

	// Get the color from the palette
	ThrowIfFailed(D2DManager::GetInstance()->GetWICImagingFactory()->CreatePalette(&pWicPalette));
	
	// Get the global palette
	ThrowIfFailed(m_decoder->CopyPalette(pWicPalette.Get()));

	ThrowIfFailed(pWicPalette->GetColors(
									ARRAYSIZE(rgColors),
									rgColors,
									&cColorsCopied));
	
	// Check whether background color is outside range 
	ThrowIfFailed((backgroundIndex >= cColorsCopied) ? E_FAIL : S_OK);

	// Get the color in ARGB format
	dwBGColor = rgColors[backgroundIndex];

	// The background color is in ARGB format, and we want to 
	// extract the alpha value and convert it in FLOAT
	FLOAT alpha = (dwBGColor >> 24) / 255.f;
	m_background_color = D2D1::ColorF(dwBGColor, alpha);

	pWicPalette = nullptr;
	return S_OK;
}

HRESULT GIFDecoder::GetFrameAndMetadata(UINT uFrameIndex, UINT width, UINT height, FrameMetadata &data, ID2D1Bitmap1* &frame)
{
	data = FrameMetadata();
	IWICFormatConverter* pConverter = nullptr;
	IWICBitmapFrameDecode* pWicFrame = nullptr;
	IWICMetadataQueryReader* pFrameMetadataQueryReader = nullptr;
	

	// Retrieve the current frame
	ThrowIfFailed(m_decoder->GetFrame(uFrameIndex, &pWicFrame));
	
	// Format convert to 32bppPBGRA which D2D expects
	ThrowIfFailed(D2DManager::GetInstance()->GetWICImagingFactory()->CreateFormatConverter(&pConverter));

	ThrowIfFailed(pConverter->Initialize(
									pWicFrame,
									GUID_WICPixelFormat32bppPBGRA,
									WICBitmapDitherTypeNone,
									nullptr,
									1.f,
									WICBitmapPaletteTypeFixedWebPalette));

	if (frame == nullptr)
	{
		data.frameIndex = uFrameIndex;

		// Get Metadata Query Reader from the frame
		ThrowIfFailed(pWicFrame->GetMetadataQueryReader(&pFrameMetadataQueryReader));

		// Get the Metadata for the current frame

		// Transparency
		ThrowIfFailed(ReadBoolMetadata(pFrameMetadataQueryReader, L"/grctlext/TransparencyFlag", data.transparent));

		// Left
		ThrowIfFailed(ReadFloatMetadata(pFrameMetadataQueryReader, L"/imgdesc/Left", data.rect.left));
		
		// Top
		ThrowIfFailed(ReadFloatMetadata(pFrameMetadataQueryReader, L"/imgdesc/Top", data.rect.top));

		// Width
		ThrowIfFailed(ReadFloatMetadata(pFrameMetadataQueryReader, L"/imgdesc/Width", data.rect.right));

		// Height
		ThrowIfFailed(ReadFloatMetadata(pFrameMetadataQueryReader, L"/imgdesc/Height", data.rect.bottom));

		// Frame delay
		UINT baseDelay;
		data.delay = 0;
		
		ThrowIfFailed(ReadUIntegerMetadata(pFrameMetadataQueryReader, L"/grctlext/Delay", baseDelay));
		ThrowIfFailed(UIntMult(baseDelay, 10, &data.delay));

		if (data.delay <= 0)
		{
			data.delay = 90;
		}
		
		// Dispose method
		data.disposal = 0;
		ThrowIfFailed(ReadUCharMetadata(pFrameMetadataQueryReader, L"/grctlext/Disposal", data.disposal));

	}

		// Create a D2DBitmap from IWICBitmapSource
		if (frame == nullptr)
		{
			ID2D1Bitmap1 *tempBitmap;

			// Read the frame into temporary bitmap to save in cache scaled one later
			ThrowIfFailed(D2DManager::GetInstance()->GetD2DDeviceContext()->CreateBitmapFromWicBitmap(
																										pConverter,
																										NULL,
																										&tempBitmap));

			// Get current dpi
			float dpiX, dpiY;
			tempBitmap->GetDpi(&dpiX, &dpiY);
			
			// Set properties of the scaled bitmap
			D2D1_BITMAP_PROPERTIES1 properties;
			ZeroMemory(&properties, sizeof(D2D1_BITMAP_PROPERTIES));
			properties.pixelFormat = tempBitmap->GetPixelFormat();
			properties.dpiX = dpiX;
			properties.dpiY = dpiY;
			tempBitmap->GetColorContext(&properties.colorContext);
			properties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET;

			// Get the D2D Context 
			ID2D1DeviceContext1* context = D2DManager::GetInstance()->GetD2DDeviceContext();

			// Create the bitmap that will be store into the cache
			D2D1_SIZE_U size = D2D1::SizeU(width, height);
			ThrowIfFailed(context->CreateBitmap(size, (const void*)0, 0, properties, &frame));

			// Define this bitmap as the render target
			context->SetTarget(frame);

			// Draw the scaled version into the bitmap that will be store into the cache
			context->BeginDraw();
			
			// Compute ratio between actual bitmap and scaled one
			double widthRatio = (double)width / (double)m_width;
			double heightRatio = (double)height / (double)m_height;

			// Get the good rectangle of the frame and compute where to draw it in the final bitmap
			D2D1_RECT_F destinationRect = D2D1::RectF(static_cast<float>(data.rect.left * widthRatio),
														static_cast<float>(data.rect.top * heightRatio),
														static_cast<float>(data.rect.right * widthRatio),
														static_cast<float>(data.rect.bottom * heightRatio));

			D2D1_RECT_F sourceRect = D2D1::RectF(0.0f, 0.0f, static_cast<float>(data.rect.right - data.rect.left), static_cast<float>(data.rect.bottom - data.rect.top));

			context->DrawBitmap(tempBitmap, &destinationRect, 1, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, &sourceRect);
			ThrowIfFailed(context->EndDraw());

			// Release the temporary bitmap
			if (tempBitmap)
			{
				tempBitmap->Release();
				tempBitmap = nullptr;
			}
		}
	

	// Release local resources used
	if (pWicFrame)
	{
		pWicFrame->Release();
		pWicFrame = nullptr;
	}

	if (pFrameMetadataQueryReader)
	{
		pFrameMetadataQueryReader->Release();
		pFrameMetadataQueryReader = nullptr;
	}

	if (pConverter)
	{
		pConverter->Release();
		pConverter = nullptr;
	}

	return S_OK;
}






